`timescale 1ns/1ps
`default_nettype none

`define READ_BACK

/* NOTE:
* 本模块根据总线请求，对SDRAM进行读写操作
* 一次最多读写一行, 且不能跨行
* 读写总是按照burst长度对齐
*/

module sdram_slave (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_sdram_clk, // 150M
    input  wire         I_rst_n,
    // bus interface
    input  wire         I_write,
    input  wire         I_read,
    input  wire [10:0]  I_addr,
    input  wire [7:0]   I_wdata,
    output wire [7:0]   O_rdata,
    // sdram mux
    output wire         O_mux_req,
    input  wire         I_mux_ack,
    output wire         O_mux_cs_n,
    output wire         O_mux_ras_n,
    output wire         O_mux_cas_n,
    output wire         O_mux_we_n,
    output wire [1:0]   O_mux_ba,
    output wire [10:0]  O_mux_addr,
    output wire [31:0]  O_mux_dq_out,
    input  wire [31:0]  I_mux_dq_in,
    output wire         O_mux_dq_oe,
    output wire [3:0]   O_mux_dqm
);
//------------------------Parameter----------------------
`include "sdram_common.vh"

// offset
localparam [10:0]
    OFFSET_ADDR0 = 0,
    OFFSET_ADDR1 = 1,
    OFFSET_ADDR2 = 2,
    OFFSET_LEN   = 4,
    OFFSET_READ  = 5,
    OFFSET_WRITE = 6,
    OFFSET_STATE = 7,
    OFFSET_BUFF  = 'h400;

// fsm
localparam [3:0]
    IDLE  = 0,
    ACT   = 1,
    WAIT0 = 2,
    READ  = 3,
    WRITE = 4,
    LOOP  = 5,
    DUMMY = 6,
    PRE   = 7,
    WAIT1 = 8;

localparam
    RL = 2 + SD_MR_CL - 1, // read data latency
    BL = 1 << SD_MR_BL;    // burst length

//------------------------Local signal-------------------
// registers
reg  [20:0] sdram_addr;  // sdram读写地址
reg  [8:0]  sdram_len;   // sdram读写长度，0表示256
reg  [1:0]  sdram_read;  // 读命令
reg  [1:0]  sdram_write; // 写命令
reg         read_ram;
reg  [7:0]  rdata_buf;
reg  [1:0]  busy_buf;

// ram
wire [9:0]  ram_addr_a;
wire [7:0]  ram_data_a;
wire        ram_we_a;
wire [7:0]  ram_q_a;
wire [7:0]  ram_addr_b;
wire [31:0] ram_data_b;
wire        ram_we_b;
wire [31:0] ram_q_b;
wire        ram_read;
wire        ram_write;
reg  [7:0]  ram_addr;
reg  [RL:0] ram_write_sr;

// fsm
reg  [3:0]  state;
reg  [3:0]  next;

// sdram mux
reg  [3:0]  delay_counter;
reg  [1:0]  read_req;
reg  [1:0]  write_req;
reg         read_flag;
reg  [6:0]  burst_count;
reg         mux_req;
reg  [3:0]  mux_cmd;
reg  [1:0]  mux_ba;
reg  [10:0] mux_addr;
wire [31:0] mux_dq_out;
reg         mux_dq_oe;

//------------------------Instantiation------------------
// ram_1024x8_256x32
ram_1024x8_256x32 ram (/*{{{*/
    .address_a ( ram_addr_a ),
    .address_b ( ram_addr_b ),
    .clock_a   ( I_sclk ),
    .clock_b   ( I_sdram_clk ),
    .data_a    ( ram_data_a ),
    .data_b    ( ram_data_b ),
    .wren_a    ( ram_we_a ),
    .wren_b    ( ram_we_b ),
    .q_a       ( ram_q_a ),
    .q_b       ( ram_q_b )
);/*}}}*/

//------------------------Body---------------------------
//{{{+++++++++++++++++++++bus interface++++++++++++++++++
`ifdef READ_BACK
    assign O_rdata = read_ram? ram_q_a : rdata_buf;
`else
    assign O_rdata = ram_q_a;
`endif

// sdram_addr
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n) 
        sdram_addr   <= 1'b0;
    else if (I_write) begin
        case (I_addr)
            OFFSET_ADDR0 : sdram_addr[7:0]   <= I_wdata;
            OFFSET_ADDR1 : sdram_addr[15:8]  <= I_wdata;
            OFFSET_ADDR2 : sdram_addr[20:16] <= I_wdata[4:0];
        endcase
    end
end

// sdram_len
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        sdram_len <= 1'b0;
    else if (I_write && I_addr == OFFSET_LEN) begin
        if (I_wdata == 1'b0)
            sdram_len <= 9'd256;
        else
            sdram_len <= I_wdata;
    end
end

// sdram_read
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        sdram_read <= 1'b0;
    else if (I_write && I_addr == OFFSET_READ)
        sdram_read <= 2'b11;
    else
        sdram_read <= sdram_read << 1;
end

// sdram_write
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        sdram_write <= 1'b0;
    else if (I_write && I_addr == OFFSET_WRITE)
        sdram_write <= 2'b11;
    else
        sdram_write <= sdram_write << 1;
end

// read_ram
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        read_ram <= 1'b0;
    else
        read_ram <= I_read & I_addr[10];
end

// rdata_buf
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        rdata_buf <= 1'b0;
    else if (I_read) begin
        case (I_addr)
            OFFSET_ADDR0 : rdata_buf <= sdram_addr[7:0];
            OFFSET_ADDR1 : rdata_buf <= sdram_addr[15:8];
            OFFSET_ADDR2 : rdata_buf <= sdram_addr[20:16];
            OFFSET_LEN   : rdata_buf <= sdram_len[7:0];
            OFFSET_STATE : rdata_buf <= busy_buf[1];
        endcase
    end
end

// busy_buf
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        busy_buf <= 1'b0;
    else
        busy_buf <= {busy_buf[0], mux_req};
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
// state
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        state <= IDLE;
    else
        state <= next;
end

// next
always @(*) begin
    case (state)
        IDLE: begin
            if (read_req[1] | write_req[1])
                next = ACT;
            else
                next = IDLE;
        end

        ACT: begin
            if (I_mux_ack)
                next = WAIT0;
            else
                next = ACT;
        end

        WAIT0: begin
            // NOTE: 由于I_mux_ack拉高时ACT命令已经被接收
            // 这里可以少等待一个周期
            if (delay_counter < DELAY_RCD - 1'b1)
                next = WAIT0;
            else if (read_flag)
                next = READ;
            else
                next = WRITE;
        end

        READ: begin
            next = LOOP;
        end

        WRITE: begin
            next = LOOP;
        end

        LOOP: begin
           if (delay_counter < BL)
               next = LOOP;
           else if (burst_count == 1'b0)
               next = DUMMY;
           else if (read_flag)
               next = READ;
           else
               next = WRITE;
        end

        DUMMY: begin
            // 写完数据最后一个数据后需要等一个周期再Precharge
            next = PRE;
        end

        PRE: begin
            next = WAIT1;
        end

        WAIT1: begin
            if (delay_counter >= DELAY_RP)
                next = IDLE;
            else
                next = WAIT1;
        end

        default: begin
            next = IDLE;
        end
    endcase
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++sdram_mux++++++++++++++++++++++
assign O_mux_req    = mux_req;
assign O_mux_cs_n   = mux_cmd[3];
assign O_mux_ras_n  = mux_cmd[2];
assign O_mux_cas_n  = mux_cmd[1];
assign O_mux_we_n   = mux_cmd[0];
assign O_mux_ba     = mux_ba;
assign O_mux_addr   = mux_addr;
assign O_mux_dq_out = mux_dq_out;
assign O_mux_dq_oe  = mux_dq_oe;
assign O_mux_dqm    = 1'b0;

// delay_counter
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        delay_counter <= 1'b0;
    else if (state == ACT || state == READ || state == WRITE || state == PRE)
        delay_counter <= 2'd2;
    else
        delay_counter <= delay_counter + 1'b1;
end

// read_req
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        read_req <= 1'b0;
    else
        read_req <= {read_req[0], sdram_read[1]};
end

// write_req
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        write_req <= 1'b0;
    else
        write_req <= {write_req[0], sdram_write[1]};
end

// read_flag
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        read_flag <= 1'b0;
    else if (state == IDLE)
        read_flag <= read_req[1];
end

// burst_count
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        burst_count <= 1'b0;
    else if (state == IDLE && next == ACT)
        burst_count <= sdram_len[8:2]; // NOTE: 设置false path
    else if (state == READ || state == WRITE)
        burst_count <= burst_count - 1'b1;
end

assign mux_dq_out = ram_q_b;

// mux_req
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_req <= 1'b0;
    else if (state == ACT)
        mux_req <= 1'b1;
    else if (state == WAIT1 && next == IDLE)
        mux_req <= 1'b0;
end

// mux_cmd
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_cmd <= SD_CMD_NOP;
    else if (state == ACT) begin
        if (I_mux_ack)
            mux_cmd <= SD_CMD_NOP;
        else
            mux_cmd <= SD_CMD_ACT;
    end
    else if (state == READ)
        mux_cmd <= SD_CMD_READ;
    else if (state == WRITE)
        mux_cmd <= SD_CMD_WRIT;
    else if (state == PRE)
        mux_cmd <= SD_CMD_PRE;
    else
        mux_cmd <= SD_CMD_NOP;
end

// mux_ba
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_ba <= 1'b0;
    else if (state == IDLE && next == ACT)
        mux_ba <= sdram_addr[20:19]; // NOTE: 设置false path
end

// mux_addr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_addr <= 1'b0;
    else if (state == IDLE && next == ACT)
        mux_addr <= sdram_addr[18:8]; // NOTE: 设置false path
    else if (state == WAIT0 && next != WAIT0)
        mux_addr <= sdram_addr[7:0] - 3'd4; // NOTE: 设置false path
    else if (state == READ || state == WRITE)
        mux_addr <= mux_addr + 3'd4;
end

// mux_dq_oe
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        mux_dq_oe <= 1'b0;
    else
        mux_dq_oe <= ram_read;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++ram++++++++++++++++++++++++++++
assign ram_addr_a = I_addr[9:0];
assign ram_data_a = I_wdata;
assign ram_we_a   = I_write & I_addr[10];

assign ram_addr_b = ram_addr;
assign ram_data_b = I_mux_dq_in;
assign ram_we_b   = ram_write_sr[RL];

assign ram_read  = (state == WRITE || state == LOOP) && !read_flag;
assign ram_write = (state == READ || state == LOOP) && read_flag;

// ram_addr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_addr <= 1'b0;
    else if (ram_read || ram_write_sr[RL])
        ram_addr <= ram_addr + 1'b1;
end

// ram_write_sr
always @(posedge I_sdram_clk or negedge I_rst_n) begin
    if (~I_rst_n)
        ram_write_sr <= 1'b0;
    else
        ram_write_sr <= {ram_write_sr[RL-1:0], ram_write};
end

//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
